#!/usr/bin/python3

# ATTENTION: Yes, this can be used as a backdoor, but only for an
# adversary with access to you *physical* serial port, which means
# that you are screwed any way.

from subprocess import Popen, PIPE
from sys import argv
from json import dumps, loads
from pwd import getpwnam
from os import setgid, setuid, environ
import serial
from systemd.daemon import notify as sd_notify

def mk_switch_user_fn(uid, gid):
    def switch_user():
        setgid(gid)
        setuid(uid)
    return switch_user

def run_cmd_as_user(cmd, user):
  pwd_user = getpwnam(user)
  switch_user_fn = mk_switch_user_fn(pwd_user.pw_uid,
                                     pwd_user.pw_gid)
  # We try to create an environment identical to what's expected
  # inside Tails for the user by logging in (via `su`) as the user,
  # setting up the GNOME shell environment, and extracting the
  # environment via `env`; not that we will run `env` unconditionally
  # since the former command could fail, e.g. if GNOME is not running.
  env_cmd = '. /usr/local/lib/tails-shell-library/gnome.sh && ' + \
            'export_gnome_env ; ' + \
            'env'
  wrapped_env_cmd = "su -c '{}' {}".format(env_cmd, user)
  pipe = Popen(wrapped_env_cmd, stdout=PIPE, shell=True)
  env_data = pipe.communicate()[0].decode('utf-8')
  env = dict((line.split('=', 1) for line in env_data.splitlines()))
  cwd = env['HOME']
  return Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True, env=env, cwd=cwd,
               preexec_fn=switch_user_fn)

def main():
  dev = argv[1]
  port = serial.Serial(port = dev, baudrate = 4000000)
  if not port.isOpen():
    port.open()

  # Notify systemd that we're ready
  sd_notify('READY=1')
  sd_notify('STATUS=Processing requests...\n')

  while True:
    try:
      line = port.readline().decode('utf-8')
    except Exception as e:
      # port must be opened wrong, so we restart everything and pray
      # that it works.
      print(str(e))
      port.close()
      return main()
    try:
      id, cmd_type, user, cmd = loads(line)
    except Exception as e:
      # We had a parse/pack error, so we just send a \0 as an ACK,
      # releasing the client from blocking.
      print(str(e))
      port.write(b"\0")
      continue
    p = run_cmd_as_user(cmd, user)
    if cmd_type == "spawn":
      returncode, stdout, stderr = 0, "", ""
    else:
      stdout_b, stderr_b = p.communicate()
      stdout = stdout_b.decode('utf-8')
      stderr = stderr_b.decode('utf-8')
      returncode = p.returncode
    port.write(dumps([id, returncode, stdout, stderr]).encode('utf-8') + b"\0")

if __name__ == "__main__":
  main()
